package nl.haemers.data.serializers 
{
	import flash.errors.IllegalOperationError;
	import flash.utils.*;
	import nl.haemers.data.reflection.Accessor;
	import nl.haemers.data.reflection.IAccessor;
	import nl.haemers.data.reflection.Type;
	
	import nl.haemers.utils.Logger;
	import nl.haemers.data.constants.DataTypes;
	import nl.haemers.data.reflection.Property;
	import nl.haemers.data.serializers.ObjectSerializer;
	/**
	 * @author Jonathan Haemers
	 * @description:
	 * - The xmlparser parses xml to DTO/VO 's. You put a DTO & some XML in the parser and it will return a nice DTO.
	 * - The parser looks for Serialize() attributes in the DTO's if they aren't there for specific properties it will
	 *   use the name of the properties. 
	 * - Nested DTO's in DTO's are possible
	 * - Parser creates Arrays & Vectors and all of the datatypes(Number, int, String etc)
	 * - Flow of the serializer: Loop through data and try to match each element with properties in the ClassReference
	 * 
	 * Functions:
	 * - 
	 * 
	 * TODO:
	 * - ignore option
	 * - custom collections 
	 * - check object serializer for vars
	 * - check object serializer for methods 
	 */
	public class XMLSerializer extends BaseSerializer
	{
		protected var _regExColor			:RegExp = /^0x/i;
		protected var _properties			:Dictionary;
		
		public function XMLSerializer() 
		{
		}
		
		/** Public methods **/
		public function deserialize(xml:XML, ClassReference:Class) : Array
		{
			var type:Type = ObjectSerializer.getType(new ClassReference());
			var result:Array = new Array();
			
			if (!type.instance)
			{
				Logger.w("Couldn't find a Serialize name!\n Add [Serialize(name=\"\")] to your object.]", this);
				throw new IllegalOperationError("Couldn't find a Serialize name!\n Add [Serialize(name=\"\")] to your object.]");
				return null;
			}
			
			for each(var node:XML in xml.elements(type.name))
			{
				var object:Object = parseComplexNode(node, new ClassReference(), type);
				result.push(object);
			}
			
			return result;
		}
		
		/** Protected methods **/
		//Parse node with nodes in it
		protected function parseComplexNode(node:XML, refObject:Object, type:Type) : Object
		{
			//first check for attributes:
			if (node.attributes().length() > 0)
			{
				refObject = parseAttributes(node, refObject, type);
			}
			
			for each(var childNode:XML in node.children())
			{
				if (childNode.hasComplexContent())
				{
					var accessor:IAccessor = type.accessors.getAccessor(childNode.name());
					if (accessor && accessor.hasChild())
					{
						//this could mean 3 things
						//It is an array/vector/object
						//TODO: when we are going to support methods. We need extra parameters to define the custom args & custom methodnames to insert the data
						var tempType:Type = accessor.objectType;
						//refObject[accessor.name] = parseComplexNode(childNode, tempType.instance, tempType);
						
						switch(accessor.dataType)
						{
							case DataTypes.ARRAY:
							case DataTypes.VECTOR:
								refObject[accessor.name] = parseCollection(childNode, tempType.instance, tempType);
								//refObject.push(parseComplexNode(childNode, tempType.instance, tempType));
							break;
							default: 
								refObject[accessor.name] = parseComplexNode(childNode, tempType.instance, tempType);
							break;
						}
						
					}
					else
					{
						refObject = parseComplexNode(childNode, refObject, type);
					}
				}
				else if(childNode.hasSimpleContent())
				{
					refObject = parseSimpleNode(childNode, refObject, type);
				}
			}
			
			return refObject;
		}
		
		//parse collection
		protected function parseCollection(node:XML, refObject:Object, type:Type) : Object
		{
			var tType:Type = type.collectionType ? type.collectionType : type;
			for each(var childNode:XML in node.children())
			{
				var collectionType:Object = type.collectionType ? tType.instance : { };
				if (childNode.hasComplexContent())
				{
					refObject.push(parseComplexNode(childNode, collectionType, tType));
				}
				else
				{
					refObject.push(parseSimpleNode(childNode, collectionType, tType));
				}
			}
			
			return refObject;
		}
		
		//Parse node with only a value
		protected function parseSimpleNode(node:XML, refObject:Object, type:Type) : Object
		{
			if(node.attributes().length() > 0)
			{
				refObject = parseAttributes(node, refObject, type);
			}
			Logger.i("Node name: " + node.name());
			if (type.accessors.hasAccessor(node.name()))
			{
				var accessor:IAccessor = type.accessors.getAccessor(node.name());
				Logger.i("Parse node: " + accessor.name);
				refObject[accessor.name] = parseValue(node.toString(), accessor.type);
			}
				
			return refObject;
		}
		
		//Parse the attributes of the node
		protected function parseAttributes(node:XML, refObject:Object, type:Type) : Object
		{
			//node has attributes let's check them out
			for each(var attr:XML in node.attributes())
			{
				//get attribute name
				var attrName:String = attr.name().toString();
				//check if referenced object has the attribute
				if (type.accessors.hasAccessor(attrName))
				{
					var accessor:IAccessor = type.accessors.getAccessor(attrName);
					Logger.i("Parse Attributes: " + attrName);
					
					refObject[accessor.name] = parseValue(attr.toString(), accessor.type);
					Logger.i(accessor.name+":"+refObject[accessor.name]);
				}
			}
			
			return refObject;
		}
		
		protected function parseValue(value:String, type:String) : *
		{
			switch(type)
			{
				case DataTypes.ARRAY:
					//not sure we need this
					return new Array();
					break;
				case DataTypes.BOOLEAN:
					//it's a boolean
					return (value == "true");
					break;
				case DataTypes.INT:
					return parseInt(value);
					break;
				case DataTypes.NUMBER:
					value = value.replace(/,/g, ".");
					return parseFloat(value);
					break;
				case DataTypes.STRING:
					return value;
					break;
				case DataTypes.UINT:
					return parseInt(value);
					break;
				case DataTypes.VECTOR:
					//not sure we need this
					break;
				default:
					return value;
					break;
			}
		}
		
		/** Private methods **/
	}
	
}